package com.ruicar.afs.cloud.account.common.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ruicar.afs.cloud.account.common.entity.*;
import com.ruicar.afs.cloud.account.common.mapper.VoucherRuleExpressAtomMapper;
import com.ruicar.afs.cloud.account.common.mapper.VoucherRuleExpressMapper;
import com.ruicar.afs.cloud.account.common.mapper.VoucherRuleInputMappingMapper;
import com.ruicar.afs.cloud.account.common.mapper.VoucherRuleItemMapper;
import com.ruicar.afs.cloud.account.common.service.VoucherEntryService;
import com.ruicar.afs.cloud.account.common.service.VoucherRuleInfoService;
import com.ruicar.afs.cloud.account.common.util.RuleConfigUtil;
import com.ruicar.afs.cloud.account.dto.atom.VoucherRuleInfoDto;
import com.ruicar.afs.cloud.account.dto.tree.RuleTreeDto;
import com.ruicar.afs.cloud.account.dto.tree.*;
import com.ruicar.afs.cloud.account.enums.rules.AtomLocation;
import com.ruicar.afs.cloud.bizcommon.enums.rules.RuleTreeExpressType;
import com.ruicar.afs.cloud.common.core.constant.CommonConstants;
import com.ruicar.afs.cloud.common.core.enums.AfsEnumUtil;
import com.ruicar.afs.cloud.common.core.uid.UidGenerator;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author infinity
 * @version 1.0
 * @date 2020/7/1 16:25
 */
@Service
@Slf4j
@AllArgsConstructor
public class VoucherRuleInfoServiceImpl implements VoucherRuleInfoService {
    /**
     * voucher rule item mapper
     * <p>Description: </p>
     */
    private final VoucherRuleItemMapper voucherRuleItemMapper;
    /**
     * voucher rule express mapper
     * <p>Description: </p>
     */
    private final VoucherRuleExpressMapper voucherRuleExpressMapper;
    /**
     * voucher rule express atom mapper
     * <p>Description: </p>
     */
    private final VoucherRuleExpressAtomMapper voucherRuleExpressAtomMapper;
    /**
     * voucher rule input mapping mapper
     * <p>Description: </p>
     */
    private final VoucherRuleInputMappingMapper voucherRuleInputMappingMapper;
    /**
     * Uid generator
     * <p>Description: </p>
     */
    private final UidGenerator uidGenerator;

    private final VoucherEntryService voucherEntryService;


    /**
     * Save voucher rule item *
     *
     * @param ruleTreeDto rule tree dto
     * @param ruleId      rule id
     * @param parentId    parent id
     * @param atomDtoMap  atom dto map
     */
    private void processAfsRuleItem(RuleTreeDto ruleTreeDto, Long ruleId, Long parentId, Map<String, RuleTreeExpressAtomDto> atomDtoMap, boolean saveToDb) {
        if (ruleTreeDto.getRows() == null)
            return;
        for (int i = 0; i < ruleTreeDto.getRows().size(); i++) {
            VoucherRuleItem voucherRuleItem = new VoucherRuleItem();
            voucherRuleItem.setRuleId(ruleId);
            voucherRuleItem.setItemType(ruleTreeDto.getRows().get(i).getType());
            voucherRuleItem.setItemLabel(ruleTreeDto.getRows().get(i).getLabel());
            voucherRuleItem.setItemKey(ruleTreeDto.getRows().get(i).getKey());
            voucherRuleItem.setItemNot(ruleTreeDto.getRows().get(i).isNot());
            voucherRuleItem.setItemSortOrder(i);
            voucherRuleItem.setParentId(parentId);
            if (saveToDb) {
                voucherRuleItemMapper.insert(voucherRuleItem);
            }
            processItemExpress(ruleTreeDto.getRows().get(i).getExpress(), ruleId, voucherRuleItem.getId(), atomDtoMap, saveToDb);
            if (ruleTreeDto.getRows().size() > 0) {
                processAfsRuleItem(ruleTreeDto.getRows().get(i), ruleId, voucherRuleItem.getId(), atomDtoMap, saveToDb);
            }
        }
    }

    /**
     * Process item express voucher rule express
     * <p>Description: </p>
     *
     * @param ruleTreeExpressDto rule tree express dto
     * @param ruleId             rule id
     * @param itemId             item id
     * @param atomDtoMap         atom dto map
     * @return the voucher rule express
     */
    private VoucherRuleExpress processItemExpress(RuleTreeExpressDto ruleTreeExpressDto, Long ruleId, Long itemId, Map<String, RuleTreeExpressAtomDto> atomDtoMap, boolean saveToDb) {
        if (ruleTreeExpressDto == null) {
            return null;
        }
        VoucherRuleExpress save = new VoucherRuleExpress();
        save.setId(uidGenerator.getUID());
        save.setRuleId(ruleId);
        save.setItemId(itemId);
        save.setLeftType(ruleTreeExpressDto.getLeft().getType());
        save.setLeftReferenceId(null);
        save.setOperatorInfo(JSON.toJSONString(ruleTreeExpressDto.getOperator()));
        save.setRightType(ruleTreeExpressDto.getRight().getType());
        save.setRightReferenceId(null);
        save.setRightValue(ruleTreeExpressDto.getRight().getValue());
        save.setRightValueNames(ruleTreeExpressDto.getRight().getValueNames());
        if (RuleTreeExpressType.EXPRESS == ruleTreeExpressDto.getLeft().getType()) {
            VoucherRuleExpress left = processItemExpress(ruleTreeExpressDto.getLeft().getExpress(), ruleId, itemId, atomDtoMap, saveToDb);
            save.setLeftReferenceId(left.getId());
        } else if (RuleTreeExpressType.ATOM == ruleTreeExpressDto.getLeft().getType()) {
            VoucherRuleExpressAtom voucherRuleExpressAtom = new VoucherRuleExpressAtom();
            voucherRuleExpressAtom.setId(uidGenerator.getUID());
            voucherRuleExpressAtom.setExpressId(save.getId());
            voucherRuleExpressAtom.setRuleId(ruleId);
            voucherRuleExpressAtom.setAtomLocation(AtomLocation.LEFT);
            voucherRuleExpressAtom.setAtomInfo(JSON.toJSONString(ruleTreeExpressDto.getLeft().getAtom()));
            if (saveToDb) {
                voucherRuleExpressAtomMapper.insert(voucherRuleExpressAtom);
            }
            atomDtoMap.put(ruleTreeExpressDto.getLeft().getAtom().getKey(), ruleTreeExpressDto.getLeft().getAtom());
        }

        if (RuleTreeExpressType.EXPRESS == ruleTreeExpressDto.getRight().getType()) {
            VoucherRuleExpress right = processItemExpress(ruleTreeExpressDto.getRight().getExpress(), ruleId, itemId, atomDtoMap, saveToDb);
            save.setRightReferenceId(right.getId());
        } else if (RuleTreeExpressType.ATOM == ruleTreeExpressDto.getRight().getType()) {
            VoucherRuleExpressAtom voucherRuleExpressAtom = new VoucherRuleExpressAtom();
            voucherRuleExpressAtom.setId(uidGenerator.getUID());
            voucherRuleExpressAtom.setExpressId(save.getId());
            voucherRuleExpressAtom.setRuleId(ruleId);
            voucherRuleExpressAtom.setAtomLocation(AtomLocation.RIGHT);
            voucherRuleExpressAtom.setAtomInfo(JSON.toJSONString(ruleTreeExpressDto.getRight().getAtom()));
            if (saveToDb) {
                voucherRuleExpressAtomMapper.insert(voucherRuleExpressAtom);
            }
            atomDtoMap.put(ruleTreeExpressDto.getRight().getAtom().getKey(), ruleTreeExpressDto.getRight().getAtom());
        }
        if (saveToDb) {
            voucherRuleExpressMapper.insert(save);
        }
        return save;
    }

    @Override
    public VoucherRuleInfoDto checkRule(VoucherRuleInfoDto voucherRuleInfoDto) {
        List<RuleTreeDto> ruleTree = RuleConfigUtil.genRuleItemTree(voucherRuleInfoDto.getRuleData());
        Assert.isTrue(ruleTree != null && ruleTree.size() > 0, "规则内容为空");
        voucherRuleInfoDto.setValidate(this.checkVoucherRule(ruleTree));
        voucherRuleInfoDto.setRuleData(null);
        return voucherRuleInfoDto;
    }

    private Boolean checkVoucherRule(List<RuleTreeDto> ruleTree ) {
        Map<String, RuleTreeExpressAtomDto> atomDtoMap = new HashMap<>();
        Map<String, String> atomKeyMapping = new HashMap<>();
        DefaultContext context = new DefaultContext();
        for (int i = 0; i < ruleTree.size(); i++) {
            processItemExpress(ruleTree.get(i).getExpress(), null, null, atomDtoMap, true);
            processAfsRuleItem(ruleTree.get(i), null, null, atomDtoMap, true);
        }
        atomDtoMap.entrySet().forEach(entry -> {
            atomKeyMapping.put(entry.getKey(), entry.getKey().replaceAll("\\.", "_") + "_" + RandomStringUtils.randomAlphanumeric(10));
            context.put(atomKeyMapping.get(entry.getKey()), new BigDecimal(1000));
        });
        try {
            String qlExpress = RuleConfigUtil.genRuleRunExpress(ruleTree, atomKeyMapping);
            log.info("校验规则=======>生成执行表达式为{}", qlExpress);
            log.info("校验规则=======>传入参数为{}", context);
            ExpressRunner runner = new ExpressRunner();
            runner.execute(qlExpress, context, null, true, true);
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("规则校验失败", e);
            return Boolean.FALSE;
        }
    }

    /**
     * Check and save rule voucher rule info dto
     * <p>Description: </p>
     *
     * @param voucherRuleInfoDto voucher rule info dto
     * @return the voucher rule info dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public VoucherRuleInfoDto checkAndSaveRule(VoucherRuleInfoDto voucherRuleInfoDto) {
        List<RuleTreeDto> ruleTree = RuleConfigUtil.genRuleItemTree(voucherRuleInfoDto.getRuleData());
        Assert.isTrue(ruleTree != null && ruleTree.size() > 0, "规则内容为空");
        Assert.isTrue(this.checkVoucherRule(ruleTree),"规则校验失败");
        VoucherEntry voucherEntry = voucherEntryService.getById(voucherRuleInfoDto.getEntryId());
        VoucherEntry oldDbVoucherEntry = voucherEntryService.getOne(Wrappers.<VoucherEntry>lambdaQuery().eq(VoucherEntry::getRuleNo, voucherRuleInfoDto.getRuleNo()).ne(VoucherEntry::getId, voucherEntry.getId()), false);
        Assert.isTrue(!(oldDbVoucherEntry != null && voucherRuleInfoDto.getId() == null), "规则编号不能重复");
        if (voucherEntry.getRuleNo() == null) {
            voucherEntry.setRuleNo(uidGenerator.getUID());
        }
        voucherEntry.setChineseLang(voucherRuleInfoDto.getChineseLang());
        voucherEntry.setProgramLang(voucherRuleInfoDto.getProgramLang());
        voucherRuleInfoDto.setId(voucherRuleInfoDto.getEntryId());
        //删除VoucherRuleExpress 信息
        voucherRuleExpressMapper.delete(Wrappers.<VoucherRuleExpress>lambdaQuery().eq(VoucherRuleExpress::getRuleId, voucherRuleInfoDto.getId()));
        //删除VoucherRuleExpressAtom 信息
        voucherRuleExpressAtomMapper.delete(Wrappers.<VoucherRuleExpressAtom>lambdaQuery().eq(VoucherRuleExpressAtom::getRuleId, voucherRuleInfoDto.getId()));
        //保存VoucherRuleItem 信息
        voucherRuleItemMapper.delete(Wrappers.<VoucherRuleItem>lambdaQuery().eq(VoucherRuleItem::getRuleId, voucherRuleInfoDto.getId()));
        Map<String, RuleTreeExpressAtomDto> atomDtoMap = new HashMap<>();
        Map<String, String> atomKeyMapping = new HashMap<>();
        for (int i = 0; i < ruleTree.size(); i++) {
            VoucherRuleItem voucherRuleItem = new VoucherRuleItem();
            voucherRuleItem.setRuleId(voucherRuleInfoDto.getId());
            voucherRuleItem.setItemType(ruleTree.get(i).getType());
            voucherRuleItem.setItemLabel(ruleTree.get(i).getLabel());
            voucherRuleItem.setItemKey(ruleTree.get(i).getKey());
            voucherRuleItem.setItemNot(ruleTree.get(i).isNot());
            voucherRuleItem.setItemSortOrder(i);
            voucherRuleItem.setParentId(CommonConstants.PARENT_ID);
            voucherRuleItemMapper.insert(voucherRuleItem);
            processItemExpress(ruleTree.get(i).getExpress(), voucherRuleInfoDto.getId(), voucherRuleItem.getId(), atomDtoMap, true);
            processAfsRuleItem(ruleTree.get(i), voucherRuleInfoDto.getId(), voucherRuleItem.getId(), atomDtoMap, true);
        }

        //保存值映射
        voucherRuleInputMappingMapper.delete(Wrappers.<VoucherRuleInputMapping>lambdaQuery().eq(VoucherRuleInputMapping::getRuleId, voucherRuleInfoDto.getId()));
        atomDtoMap.entrySet().forEach(entry -> {
            VoucherRuleInputMapping voucherRuleInputMapping = new VoucherRuleInputMapping();
            voucherRuleInputMapping.setAtomKey(entry.getKey());
            voucherRuleInputMapping.setAtomName(entry.getValue().getName());
            voucherRuleInputMapping.setAtomType(entry.getValue().getComponent());
            voucherRuleInputMapping.setRuleId(voucherRuleInfoDto.getId());
            voucherRuleInputMapping.setValueExpress(entry.getValue().getValueExpress());
            voucherRuleInputMapping.setMappingKey(entry.getKey().replaceAll("\\.", "_") + "_" + RandomStringUtils.randomAlphanumeric(10));
            atomKeyMapping.put(entry.getKey(), voucherRuleInputMapping.getMappingKey());
            voucherRuleInputMappingMapper.insert(voucherRuleInputMapping);
        });
        voucherEntry.setAmountExpression(RuleConfigUtil.genRuleRunExpress(ruleTree, atomKeyMapping));
        voucherEntryService.updateById(voucherEntry);
        voucherRuleInfoDto.setRuleData(null);
        return voucherRuleInfoDto;
    }

    @Override
    public List<RuleTreeDto> loadRuleTree(Long ruleId) {
        List<VoucherRuleItem> items = voucherRuleItemMapper
                .selectList(Wrappers.<VoucherRuleItem>lambdaQuery().eq(VoucherRuleItem::getRuleId, ruleId));

        Map<Long, List<RuleTreeDto>> parentIdMap = new HashMap<>();
        Set<Long> itemIdSet = new HashSet<>();
        List<RuleTreeDto> ruleTreeDtoList = items
                .stream()
                .map(voucherRuleItem -> {
                    RuleTreeDto ruleTreeDto = new RuleTreeDto();
                    ruleTreeDto.setId(voucherRuleItem.getId());
                    ruleTreeDto.setRuleId(ruleId);
                    ruleTreeDto.setType(voucherRuleItem.getItemType());
                    ruleTreeDto.setLabel(voucherRuleItem.getItemLabel());
                    ruleTreeDto.setKey(voucherRuleItem.getItemKey());
                    ruleTreeDto.setNot(voucherRuleItem.isItemNot());
                    ruleTreeDto.setParentId(voucherRuleItem.getParentId());
                    ruleTreeDto.setRows(new ArrayList<>());
                    ruleTreeDto.setExpress(null);
                    if (!parentIdMap.containsKey(voucherRuleItem.getParentId())) {
                        parentIdMap.put(voucherRuleItem.getParentId(), new ArrayList<>());
                    }
                    parentIdMap.get(voucherRuleItem.getParentId()).add(ruleTreeDto);
                    return ruleTreeDto;
                }).collect(Collectors.toList());

        ruleTreeDtoList.forEach(ruleTreeDto -> {
            ruleTreeDto.setRows(parentIdMap.get(ruleTreeDto.getId()));
            itemIdSet.add(ruleTreeDto.getId());
        });

        Map<Long, VoucherRuleExpress> idExpressMap = new HashMap<>();
        Map<Long, Long> itemIdExpressIdMap = new HashMap<>();
        voucherRuleExpressMapper
                .selectList(Wrappers.<VoucherRuleExpress>lambdaQuery().eq(VoucherRuleExpress::getRuleId, ruleId).in(itemIdSet.size() > 0, VoucherRuleExpress::getItemId, itemIdSet))
                .forEach(voucherRuleExpress -> {
                    idExpressMap.put(voucherRuleExpress.getId(), voucherRuleExpress);
                    // 父级express id 永远最小，因此要找到最小的映射
                    if (!itemIdExpressIdMap.containsKey(voucherRuleExpress.getItemId())) {
                        itemIdExpressIdMap.put(voucherRuleExpress.getItemId(), voucherRuleExpress.getId());
                    } else {
                        if (voucherRuleExpress.getId() < itemIdExpressIdMap.get(voucherRuleExpress.getItemId())) {
                            itemIdExpressIdMap.put(voucherRuleExpress.getItemId(), voucherRuleExpress.getId());
                        }
                    }
                });


        Map<Long, Map<String, VoucherRuleExpressAtom>> expressIdAtomsMap = new HashMap<>();

        voucherRuleExpressAtomMapper
                .selectList(Wrappers.<VoucherRuleExpressAtom>lambdaQuery().eq(VoucherRuleExpressAtom::getRuleId, ruleId).in(idExpressMap.keySet().size() > 0, VoucherRuleExpressAtom::getExpressId, idExpressMap.keySet()))
                .forEach(voucherRuleExpressAtom -> {
                    if (!expressIdAtomsMap.containsKey(voucherRuleExpressAtom.getExpressId())) {
                        expressIdAtomsMap.put(voucherRuleExpressAtom.getExpressId(), new HashMap<>());
                    }
                    expressIdAtomsMap
                            .get(voucherRuleExpressAtom.getExpressId())
                            .put(AfsEnumUtil.key(voucherRuleExpressAtom.getAtomLocation()), voucherRuleExpressAtom);
                });

        ruleTreeDtoList.forEach(ruleTreeDto -> {
            if (itemIdExpressIdMap.containsKey(ruleTreeDto.getId())) {
                ruleTreeDto.setExpress(genRuleTreeExpress(idExpressMap.get(itemIdExpressIdMap.get(ruleTreeDto.getId())), idExpressMap, expressIdAtomsMap));
            }
        });
        return parentIdMap.getOrDefault(CommonConstants.PARENT_ID, new ArrayList<>());
    }

    private RuleTreeExpressDto genRuleTreeExpress(VoucherRuleExpress express, Map<Long, VoucherRuleExpress> idExpressMap, Map<Long, Map<String, VoucherRuleExpressAtom>> expressIdAtomsMap) {
        RuleTreeExpressDto ruleTreeExpressDto = new RuleTreeExpressDto();
        RuleTreeExpressLeftDto left = new RuleTreeExpressLeftDto();
        left.setType(express.getLeftType());
        if (expressIdAtomsMap.containsKey(express.getId()) && expressIdAtomsMap.get(express.getId()).containsKey(AfsEnumUtil.key(AtomLocation.LEFT))) {
            left.setAtom(genRuleTreeExpressAtom(expressIdAtomsMap.get(express.getId()).get(AfsEnumUtil.key(AtomLocation.LEFT))));
        }
        if (express.getLeftReferenceId() != null) {
            left.setExpress(genRuleTreeExpress(idExpressMap.get(express.getLeftReferenceId()), idExpressMap, expressIdAtomsMap));
        }

        ruleTreeExpressDto.setLeft(left);

        ruleTreeExpressDto.setOperator(JSONObject.parseObject(express.getOperatorInfo(), RuleTreeExpressOperatorDto.class));

        RuleTreeExpressRightDto right = new RuleTreeExpressRightDto();

        right.setType(express.getRightType());
        right.setValue(express.getRightValue());
        right.setValueNames(express.getRightValueNames());

        if (expressIdAtomsMap.containsKey(express.getId()) && expressIdAtomsMap.get(express.getId()).containsKey(AfsEnumUtil.key(AtomLocation.RIGHT))) {
            right.setAtom(genRuleTreeExpressAtom(expressIdAtomsMap.get(express.getId()).get(AfsEnumUtil.key(AtomLocation.RIGHT))));
        }

        if (express.getRightReferenceId() != null) {
            right.setExpress(genRuleTreeExpress(idExpressMap.get(express.getRightReferenceId()), idExpressMap, expressIdAtomsMap));
        }

        ruleTreeExpressDto.setRight(right);
        return ruleTreeExpressDto;
    }

    private RuleTreeExpressAtomDto genRuleTreeExpressAtom(VoucherRuleExpressAtom voucherRuleExpressAtom) {
        return JSONObject.parseObject(voucherRuleExpressAtom.getAtomInfo(),RuleTreeExpressAtomDto.class);
    }

}
